package com.pai4j.log.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pai4j.common.bean.PAIPageResponseBeanUtil;
import com.pai4j.common.util.RedisUtil;
import com.pai4j.log.constants.LogConstants;
import com.pai4j.log.dao.IOperationLogDAO;
import com.pai4j.log.entity.OperationLogEntity;
import com.pai4j.log.enums.LogStatusEnum;
import com.pai4j.log.service.ILogService;
import com.pai4j.log.vo.LogQueryRequest;
import com.pai4j.log.vo.LogStatisticsRequest;
import com.pai4j.log.vo.LogStatisticsVO;
import com.pai4j.log.vo.OperationLogVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import jakarta.annotation.Resource;
import jakarta.persistence.criteria.Predicate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 日志服务实现类
 *
 * @author: CYM-pai
 * @date: 2025/07/29 15:50
 **/
@Slf4j
@Service
public class LogServiceImpl implements ILogService {

    @Resource
    private IOperationLogDAO operationLogDAO;

    @Resource
    private RedisUtil redisUtil;

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void saveOperationLog(OperationLogEntity logEntity) {
        try {
            if (logEntity.getCreateTime() == null) {
                logEntity.setCreateTime(new Date());
            }
            logEntity.setUpdateTime(new Date());
            operationLogDAO.save(logEntity);
        } catch (Exception e) {
            log.error("保存操作日志失败", e);
        }
    }

    @Override
    @Async("logAsyncExecutor")
    public void saveOperationLogAsync(OperationLogEntity logEntity) {
        saveOperationLog(logEntity);
    }

    @Override
    public void batchSaveOperationLogs(List<OperationLogEntity> logEntities) {
        try {
            Date now = new Date();
            logEntities.forEach(entity -> {
                if (entity.getCreateTime() == null) {
                    entity.setCreateTime(now);
                }
                entity.setUpdateTime(now);
            });
            operationLogDAO.saveAll(logEntities);
        } catch (Exception e) {
            log.error("批量保存操作日志失败", e);
        }
    }

    @Override
    public PAIPageResponseBeanUtil<List<OperationLogVO>> queryOperationLogs(LogQueryRequest request) {
        try {
            // 构建查询条件
            Specification<OperationLogEntity> spec = buildQuerySpecification(request);
            
            // 构建分页和排序
            Sort sort = buildSort(request.getSortField(), request.getSortDirection());
            Pageable pageable = PageRequest.of(request.getPageNo() - 1, request.getPageSize(), sort);
            
            // 执行查询
            Page<OperationLogEntity> page = operationLogDAO.findAll(spec, pageable);
            
            // 转换结果
            List<OperationLogVO> voList = page.getContent().stream()
                    .map(this::convertToVO)
                    .collect(Collectors.toList());
            
            return PAIPageResponseBeanUtil.success(
                    request.getPageNo(),
                    request.getPageSize(),
                    page.getTotalElements(),
                    voList
            );
        } catch (Exception e) {
            log.error("查询操作日志失败", e);
            return PAIPageResponseBeanUtil.success(
                    request.getPageNo(),
                    request.getPageSize(),
                    0L,
                    new ArrayList<>()
            );
        }
    }

    @Override
    public List<OperationLogVO> getLogsByTraceId(String traceId) {
        try {
            // 先从缓存查询
            String cacheKey = LogConstants.OPERATION_LOG_CACHE_PREFIX + "trace_" + traceId;
            String cachedJson = redisUtil.get(cacheKey);
            if (cachedJson != null && !"null".equals(cachedJson)) {
                try {
                    return objectMapper.readValue(cachedJson, new TypeReference<List<OperationLogVO>>() {});
                } catch (Exception e) {
                    log.warn("反序列化缓存数据失败，将从数据库查询", e);
                }
            }

            // 从数据库查询
            List<OperationLogEntity> entities = operationLogDAO.findByTraceIdOrderByCreateTimeAsc(traceId);
            List<OperationLogVO> voList = entities.stream()
                    .map(this::convertToVO)
                    .collect(Collectors.toList());

            // 缓存结果
            try {
                String jsonStr = objectMapper.writeValueAsString(voList);
                redisUtil.set(cacheKey, jsonStr, LogConstants.DEFAULT_CACHE_EXPIRE_TIME);
            } catch (Exception e) {
                log.warn("序列化数据到缓存失败", e);
            }

            return voList;
        } catch (Exception e) {
            log.error("根据链路追踪ID查询日志失败", e);
            return new ArrayList<>();
        }
    }

    @Override
    public List<LogStatisticsVO> getLogStatistics(LogStatisticsRequest request) {
        try {
            // 设置默认时间范围
            if (request.getStartTime() == null) {
                Calendar calendar = Calendar.getInstance();
                calendar.add(Calendar.DAY_OF_MONTH, -7);
                request.setStartTime(calendar.getTime());
            }
            if (request.getEndTime() == null) {
                request.setEndTime(new Date());
            }
            
            // 根据统计维度执行不同的查询
            List<LogStatisticsVO> statistics;
            switch (request.getDimension()) {
                case "module":
                    statistics = operationLogDAO.getModuleStatistics(request.getStartTime(), request.getEndTime());
                    break;
                case "operation":
                    statistics = operationLogDAO.getOperationStatistics(request.getStartTime(), request.getEndTime());
                    break;
                case "user":
                    statistics = operationLogDAO.getUserStatistics(request.getStartTime(), request.getEndTime());
                    break;
                default:
                    statistics = operationLogDAO.getModuleStatistics(request.getStartTime(), request.getEndTime());
                    break;
            }
            
            // 限制返回数量
            if (request.getLimit() != null && request.getLimit() > 0) {
                statistics = statistics.stream()
                        .limit(request.getLimit())
                        .collect(Collectors.toList());
            }
            
            return statistics;
        } catch (Exception e) {
            log.error("获取日志统计信息失败", e);
            return new ArrayList<>();
        }
    }

    @Override
    public Long cleanExpiredLogs(int days) {
        try {
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.DAY_OF_MONTH, -days);
            Date beforeTime = calendar.getTime();
            
            return operationLogDAO.deleteByCreateTimeBefore(beforeTime);
        } catch (Exception e) {
            log.error("清理过期日志失败", e);
            return 0L;
        }
    }

    @Override
    public OperationLogVO getLogDetail(String logId) {
        try {
            Optional<OperationLogEntity> optional = operationLogDAO.findById(logId);
            if (optional.isPresent()) {
                return convertToVO(optional.get());
            }
        } catch (Exception e) {
            log.error("获取日志详情失败", e);
        }
        return null;
    }

    /**
     * 构建查询条件
     */
    private Specification<OperationLogEntity> buildQuerySpecification(LogQueryRequest request) {
        return (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            
            if (!StringUtils.isEmpty(request.getUserId())) {
                predicates.add(criteriaBuilder.equal(root.get("userId"), request.getUserId()));
            }
            
            if (!StringUtils.isEmpty(request.getUserName())) {
                predicates.add(criteriaBuilder.like(root.get("userName"), "%" + request.getUserName() + "%"));
            }
            
            if (!StringUtils.isEmpty(request.getModule())) {
                predicates.add(criteriaBuilder.equal(root.get("module"), request.getModule()));
            }
            
            if (!StringUtils.isEmpty(request.getOperation())) {
                predicates.add(criteriaBuilder.equal(root.get("operation"), request.getOperation()));
            }
            
            if (!StringUtils.isEmpty(request.getLogType())) {
                predicates.add(criteriaBuilder.equal(root.get("logType"), request.getLogType()));
            }
            
            if (request.getStatus() != null) {
                predicates.add(criteriaBuilder.equal(root.get("status"), request.getStatus()));
            }
            
            if (!StringUtils.isEmpty(request.getTraceId())) {
                predicates.add(criteriaBuilder.equal(root.get("traceId"), request.getTraceId()));
            }
            
            if (!StringUtils.isEmpty(request.getIp())) {
                predicates.add(criteriaBuilder.equal(root.get("ip"), request.getIp()));
            }
            
            if (!StringUtils.isEmpty(request.getKeyword())) {
                Predicate keywordPredicate = criteriaBuilder.or(
                        criteriaBuilder.like(root.get("description"), "%" + request.getKeyword() + "%"),
                        criteriaBuilder.like(root.get("params"), "%" + request.getKeyword() + "%"),
                        criteriaBuilder.like(root.get("result"), "%" + request.getKeyword() + "%")
                );
                predicates.add(keywordPredicate);
            }
            
            if (request.getStartTime() != null) {
                predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createTime"), request.getStartTime()));
            }
            
            if (request.getEndTime() != null) {
                predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("createTime"), request.getEndTime()));
            }
            
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        };
    }

    /**
     * 构建排序
     */
    private Sort buildSort(String sortField, String sortDirection) {
        Sort.Direction direction = "ASC".equalsIgnoreCase(sortDirection) ? 
                Sort.Direction.ASC : Sort.Direction.DESC;
        
        if (StringUtils.isEmpty(sortField)) {
            sortField = "createTime";
        }
        
        return Sort.by(direction, sortField);
    }

    /**
     * 实体转换为VO
     */
    private OperationLogVO convertToVO(OperationLogEntity entity) {
        OperationLogVO vo = new OperationLogVO();
        BeanUtils.copyProperties(entity, vo);
        
        // 设置状态描述
        if (entity.getStatus() != null) {
            vo.setStatusDesc(entity.getStatus().equals(LogStatusEnum.SUCCESS.getCode()) ? 
                    LogStatusEnum.SUCCESS.getDescription() : LogStatusEnum.FAILURE.getDescription());
        }
        
        return vo;
    }
}
